home *** CD-ROM | disk | FTP | other *** search
/ Acorn RISC PD-CD 1 / Acorn RISC PD-CD 1.iso / languages / hope / hopetutorl < prev   
Text File  |  1990-04-25  |  55KB  |  2,113 lines

  1.  
  2.  
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.                                A HOPE TUTORIAL
  10.  
  11.                        Roger Bailey, Imperial College
  12.  
  13.  
  14.  
  15.  
  16.  
  17.  
  18.  
  19.  
  20.                     Functions In Conventional Languages:
  21.  
  22. In a language like Pascal, a function is a piece of  'packaged'  program  for
  23. performing  standard  operations  like  finding  square roots.  To obtain the
  24. square root of a positive number stored in a variable x, we write:
  25.  
  26.      sqrt ( x )
  27. at the point in the program where we want the value, such as:
  28.  
  29.  
  30.      writeln ( 1.0 + sqrt ( x ) ) ;
  31.  
  32. this is called an application of the function.  The value represented by  "x"
  33. is  called  the  argument  or  actual parameter.  In this context, the canned
  34. program computes the square root of "x", "1.0" is added to it and the  result
  35. is then printed.
  36.  
  37.  
  38. We can also define our own functions specifying how the  result  is  computed
  39. using ordinary Pascal statements.  Here's a function that returns the greater
  40. of its two argument values:
  41.  
  42.  
  43.      function max ( x, y : INTEGER ) : INTEGER ;
  44.      begin
  45.      if x > y
  46.         then max := x
  47.         else max := y
  48.      end ;
  49.  
  50.  
  51. The identifiers "x" and "y"  are  called  formal  parameters.   They're  used
  52. inside  the  definition  to  name  the  two  values  that will be supplied as
  53. arguments when the function is applied.  We can use "max" anywhere we need  a
  54. value,  just  like  "sqrt".   Here's  how  we  might  use "max" to filter out
  55. negative values on output:
  56.  
  57.  
  58.      writeln ( max ( z, 0 ) ) ;
  59.  
  60.  
  61.  
  62.  
  63. Hope Tutorial                                                          Page 1
  64.  
  65.  
  66.  
  67.  
  68.  
  69.  
  70.  
  71.  
  72.  
  73.  
  74. A  more  interesting  case  is  when  the  actual  parameter  is  a  function
  75. application  itself or involves one.  We can use "max" to find the largest of
  76. three numbers by writing:
  77.  
  78.  
  79.      max ( a, max ( b, c ) )
  80.  
  81.  
  82. Combining functions together like this is called composition.  The expression
  83. is  evaluated  'inside-out'  because  the outer application of "max" can't be
  84. evaluated until the value  of  its  second  argument  is  known.   The  inner
  85. application of "max" is therefore evaluated first using the values of "b" and
  86. "c" and the result is used as the actual parameter of the outer application.
  87.  
  88.  
  89. Another way of combining functions together is to define more  powerful  ones
  90. using  simpler  ones  as  'building  blocks'.   If  we often need to find the
  91. largest of three numbers we might define:
  92.  
  93.  
  94.      function MaxOf3 ( x, y, z : INTEGER ) : INTEGER ;
  95.      begin
  96.        MaxOf3 := max ( x, max ( y, z ) )
  97.      end ;
  98.  
  99.  
  100. and apply it by writing:
  101.  
  102.  
  103.      MaxOf3 ( a, b, c )
  104.  
  105.  
  106.  
  107.  
  108.  
  109.  
  110.                          Programming with functions
  111.  
  112. Pascal is called an imperative language because programs written  in  it  are
  113. recipes for 'doing something'.  If our programs consist only of functions, we
  114. can concentrate on what the results are  and  ignore  how  they're  computed.
  115. Forget  that  "sqrt" is a piece of code and think of "sqrt ( x )" as a way of
  116. writing a value in your program, and you'll get the idea.  You can  think  of
  117. "MaxOf3"  like  this  as  well  if  you  ignore  the way it works inside.  By
  118. defining a 'toolkit' of useful functions and  combining  them  together  like
  119. this,  we  can  build  powerful  programs  that  are  quite short and easy to
  120. understand.
  121.  
  122.  
  123.  
  124.  
  125.  
  126.  
  127.  
  128.  
  129. Hope Tutorial                                                          Page 2
  130.  
  131.  
  132.  
  133.  
  134.  
  135.  
  136.  
  137.  
  138.  
  139.  
  140. In Pascal, functions can only return 'simple' data objects such as numbers or
  141. characters,  but  real  programs  use big data structures and can't easily be
  142. written using these functions.  In Hope, functions can  return  any  type  of
  143. value,  including  data  structures equivalent to Pascal's arrays and records
  144. and much more.  Programming in Hope has the flavour of simply  'writing  down
  145. the  answer' by writing an expression that defines it.  This will contain one
  146. or more function applications to define smaller parts of the  answer.   These
  147. functions won't usually be built in like "sqrt", so we'll have to define them
  148. ourselves, but we'll still think of them as definitions of data objects,  and
  149. not as algorithms for computing them.
  150.  
  151.  
  152.  
  153.  
  154.  
  155.  
  156.                     A simple Hope example -- conditionals
  157.  
  158. Let's  see  how  we  can  define  "max"  in  Hope.   Like  Pascal,   it's   a
  159. strongly-typed  language,  which  means  we  must tell the compiler about the
  160. types of all objects in our programs  so  it  can  check  that  they're  used
  161. consistently.   The function definition comes in two parts.  First we declare
  162. the argument and result types:
  163.  
  164.  
  165.      dec max : num # num -> num ;
  166.  
  167.  
  168. "dec" is in bold face because it's a reserved word and you can't use it as  a
  169. name.   Type  it  in lower case when entering programs.  "max" is the name of
  170. the function being defined.  Names consist of upper and  lower  case  letters
  171. (which  are  distinct) and digits, and must start with a letter.  The current
  172. fashion is to use lower case.  The  layout  isn't  significant  and  you  can
  173. separate symbols with any number of blanks, tabs and newlines for clarity, as
  174. in this example.  Symbols need only be separated when they might otherwise be
  175. confused as one, such as "dec" and "max".
  176.  
  177.  
  178. The next part of the declaration gives the types of the arguments  (read  the
  179. symbol  ":"  as 'takes a').  Non-negative integers are of the predefined type
  180. "num" (in lower case).  "#" is read as 'and a'; (or you can use the  reserved
  181. word  "X").   "->"  is  read as 'yields'.  The semicolon marks the end of the
  182. declaration, which tells  the  compiler  that  "max"  takes  two  numbers  as
  183. arguments and returns a single number as its result.
  184.  
  185.  
  186. The result of a function is defined  by  one  or  more  recursion  equations.
  187. "max" needs only one equation to define it:
  188.  
  189.  
  190.      --- max ( x, y ) <=  if x > y then x else y ;
  191.  
  192.  
  193.  
  194.  
  195. Hope Tutorial                                                          Page 3
  196.  
  197.  
  198.  
  199.  
  200.  
  201.  
  202.  
  203.  
  204.  
  205.  
  206. Read the symbol "---" as 'the value of'.  The expression "max ( x,  y  )"  is
  207. called  the left-hand side of the equation.  It defines "x" and "y" as formal
  208. parameters, or local names for the values that  will  be  supplied  when  the
  209. function  is  applied.  Parameter names are local to the equation, so "x" and
  210. "y" won't be confused with any other "x" or "y" in the program.   The  symbol
  211. "<=" is read as 'is defined as'.
  212.  
  213.  
  214. The rest of the equation (called the right-hand  side)  defines  the  result.
  215. It's  a  conditional  expression.   The  symbols  "if", "then" and "else" are
  216. reserved words.  Pascal's conditional statement chooses  between  alternative
  217. actions,  but  Hope's  conditional  expression  chooses  between  alternative
  218. values, in line with our view that function applications are ways of  writing
  219. values  rather  than  recipes  for  computing  them.   If  the  value  of the
  220. expression "x > y" is "true", the value of the whole  conditional  expression
  221. is the value of "x", otherwise it's the value of "y".  The alternative values
  222. can be defined by any Hope expressions.
  223.  
  224.  
  225. When the value of a function is defined by  more  than  one  expression  like
  226. this,  they  are  evaluated in an unspecified order.  On a suitable computer,
  227. such as the Imperial College ALICE machine, it's even  possible  to  evaluate
  228. both  expressions  and  the test in parallel and throw away one of the values
  229. according to the result of the test.
  230.  
  231.  
  232.  
  233.  
  234.  
  235.  
  236.                      Using functions that we've defined
  237.  
  238. A Hope program is just a a single expression containing one or more  function
  239. applications  composed  together.   It's evaluated immediately and the result
  240. and its type are printed on the screen.  Here's a simple  program  that  uses
  241. "max", with its output (which is shown in italics):
  242.  
  243.  
  244.      max ( 10, 20 ) + max ( 1, max ( 2,3 ) ) ;
  245.      23 : num
  246.  
  247.  
  248. The rules for evaluating the expression are the  same  as  those  of  Pascal:
  249. function  arguments  are  evaluated  first,  the  functions  are applied, and
  250. finally other operations are performed in the usual order of priority.
  251.  
  252.  
  253.  
  254.  
  255.  
  256.  
  257.  
  258.  
  259.  
  260.  
  261. Hope Tutorial                                                          Page 4
  262.  
  263.  
  264.  
  265.  
  266.  
  267.  
  268.  
  269.  
  270.  
  271.  
  272. We can also use existing functions to  define  new  ones.   Here's  the  Hope
  273. version of "MaxOf3":
  274.  
  275.  
  276.      dec MaxOf3 : num # num # num -> num ;
  277.      --- MaxOf3 ( x, y, z ) <= max ( x, max ( y, z ) ) ;
  278.  
  279.  
  280.  
  281.  
  282.  
  283.  
  284.                    A more interesting example - repetition
  285.  
  286. Just as Pascal's conditional statement  is  replaced  by  Hope's  conditional
  287. value,  so  the  repetitive  statement  is  replaced by the repetitive value.
  288. Here's a Pascal function that multiplies two numbers using repeated addition:
  289.  
  290.  
  291.      function mult ( x, y : INTEGER ) : INTEGER ;
  292.      var prod  : INTEGER ;
  293.  
  294.      begin
  295.        prod := 0 ;
  296.        while y > 0 do
  297.          begin
  298.       prod := prod + x ;
  299.       y := y - 1
  300.          end ;
  301.        mult := prod
  302.      end ;
  303.  
  304.  
  305. It's hard to be sure this function does enough additions (it  took  me  three
  306. tries  to  get it right) and this seems to be a general problem with loops in
  307. programs.  A common way of checking imperative programs is to simulate  their
  308. execution.  If we do this for input values of 2 and 3, we'll find that "prod"
  309. starts with the value 0 and gets values of 2, 4  and  6  on  successive  loop
  310. iterations, which suggests the definition is correct.
  311.  
  312.  
  313.  
  314.  
  315.  
  316.  
  317.  
  318.  
  319.  
  320.  
  321.  
  322.  
  323.  
  324.  
  325.  
  326.  
  327. Hope Tutorial                                                          Page 5
  328.  
  329.  
  330.  
  331.  
  332.  
  333.  
  334.  
  335.  
  336.  
  337.  
  338. Hope doesn't have any loops, so we must write  all  the  additions  that  the
  339. Pascal  program  performed  in  a single expression.  It's much easier to see
  340. that this has the right number of additions:
  341.  
  342.  
  343.      dec mult : num # num -> num ;
  344.      --- mult ( x, y ) <= 0 + x + x + ...
  345.  
  346.  
  347. or would be if we knew how many times to write "+ x".   The  hand  simulation
  348. suggests  we  need  to write it "y" times, which is tricky when we don't know
  349. the value of "y".  What we do know is that for a  given  value  of  "y",  the
  350. expressions:
  351.  
  352.  
  353.      mult ( x, y )        and       mult ( x, y-1 ) + x
  354.  
  355.  
  356. will have the same number of "+ x" terms if written out in full.  The  second
  357. one  always  has two terms, whatever the value of "y", so we'll use it as the
  358. definition of "mult":
  359.  
  360.  
  361.      --- mult ( x, y ) <= mult ( x, y-1 ) + x ;
  362.  
  363.  
  364. On the face of it we've written something ridiculous,  because  it  means  we
  365. must apply "mult" to find the value of "mult".  Remember however that this is
  366. really shorthand for "0" followed by "y" occurrences of "+ x".  When  "y"  is
  367. zero, the result of "mult" is also zero because there are no "+ x" terms.  In
  368. this case "mult" isn't defined in terms of itself, so if  we  add  a  special
  369. test for it, the definition terminates.  A usable definition of "mult" is:
  370.  
  371.  
  372.      --- mult ( x, y ) <= if y = 0 then 0 else mult ( x, y-1 ) + x ;
  373.  
  374.  
  375. Functions that are defined using themselves like this are  called  recursive.
  376. Every Pascal program using a loop can be expressed as a recursive function in
  377. Hope.  All recursive definitions need one case (called the base  case)  where
  378. the  function  isn't  defined in terms of itself, just as Pascal loops need a
  379. terminating condition.
  380.  
  381.  
  382.  
  383.  
  384.  
  385.  
  386.  
  387.  
  388.  
  389.  
  390.  
  391.  
  392.  
  393. Hope Tutorial                                                          Page 6
  394.  
  395.  
  396.  
  397.  
  398.  
  399.  
  400.  
  401.  
  402.  
  403.  
  404.                        Another way of using functions
  405.  
  406. Hope allows us to use a function with two arguments like "mult" as  an  infix
  407. operator.   We must assign it a priority and use it as an operator everywhere
  408. including the equations that definine it.  The definition of "mult" when used
  409. as an infix operator looks like this:
  410.  
  411.  
  412.      infix mult : 8 ;
  413.      dec mult : num # num -> num ;
  414.      --- x mult y <= if y = 0 then 0 else x mult ( y - 1 ) + x ;
  415.  
  416.  
  417. A bigger number in the infix declaration means a higher priority.  The second
  418. argument of "mult" is parenthesised because its priority of 8 is greater than
  419. the built-in subtraction operation.  Most of Hope's  standard  functions  are
  420. supplied as infix operators.
  421.  
  422.  
  423.  
  424.  
  425.  
  426.  
  427.                              Other kinds of data
  428.  
  429.  
  430. Hope provides two other primitive data types.  A "truval"  (truth  value)  is
  431. equivalent  to  a  Pascal  Boolean  and has values "true" and "false".  We've
  432. already seen the expression "x >  y"  defining  a  truth  value.   ">"  is  a
  433. standard  function  whose  type  is  "num # num -> truval".  We can use truth
  434. values in conditional expressions and combine them together with the standard
  435. functions "and", "or" and "not".
  436.  
  437.  
  438. Single characters are of type "char", with values "'a'",  "'b'"  and  so  on.
  439. Characters  are  most  useful  as  components  of  data  structures  such  as
  440. character-strings.
  441.  
  442.  
  443.  
  444.  
  445.  
  446.  
  447.  
  448.  
  449.  
  450.  
  451.  
  452.  
  453.  
  454.  
  455.  
  456.  
  457.  
  458.  
  459. Hope Tutorial                                                          Page 7
  460.  
  461.  
  462.  
  463.  
  464.  
  465.  
  466.  
  467.  
  468.  
  469.  
  470.                                Data structures
  471.  
  472. Practical programs need data structures  and  Hope  has  two  standard  kinds
  473. already  built  in.  The simplest kind corresponds to a Pascal record. We can
  474. bind a fixed number of objects of any type together into a structure called a
  475. tuple, for example:
  476.  
  477.  
  478.      ( 2, 3 )       or        ( 'a', true )
  479.  
  480.  
  481. are tuples of type "num # num" and "char  #  truval"  respectively.   We  use
  482. tuples  when  we  want  a function to define more than one value.  Here's one
  483. that defines the time of day given the number of seconds since midnight:
  484.  
  485.  
  486.      dec time24 : num -> num # num # num ;
  487.      --- time24 ( s ) <= ( s div 3600,
  488.                       s mod 3600 div 60,
  489.                       s mod 3600 mod 60  ) ;
  490.  
  491.  
  492. "div" is the built-in integer division function and "mod" gives the remainder
  493. after  integer  division.   If  we  type  an  application  of "time24" at the
  494. terminal, the result tuple and its type will be printed on the screen in  the
  495. usual way:
  496.  
  497.  
  498.      time24 ( 45756 ) ;
  499.      ( 12,42,36 ) : ( num # num # num )
  500.  
  501.  
  502.  
  503.  
  504.  
  505.  
  506.  
  507.  
  508.  
  509.  
  510.  
  511.  
  512.  
  513.  
  514.  
  515.  
  516.  
  517.  
  518.  
  519.  
  520.  
  521.  
  522.  
  523.  
  524.  
  525. Hope Tutorial                                                          Page 8
  526.  
  527.  
  528.  
  529.  
  530.  
  531.  
  532.  
  533.  
  534.  
  535.  
  536. The second standard data type,  called  a  list,  corresponds  roughly  to  a
  537. one-dimensional  array  in  Pascal.   It  can  contain  any number of objects
  538. (including none at all) but they must all be the same  type.   We  can  write
  539. expressions in our programs that represent lists, such as:
  540.  
  541.  
  542.      [ 1, 2, 3 ]
  543.  
  544.  
  545. which is of type "list ( num  )".   There  are  two  standard  functions  for
  546. defining  lists.   The infix operator "::" (read as 'cons') defines a list in
  547. terms of a single object and list containing the same type of object, so
  548.  
  549.  
  550.      10 :: [ 20, 30, 40 ]
  551.  
  552.  
  553. defines the list:
  554.  
  555.  
  556.      [ 10, 20, 30, 40 ]
  557.  
  558.  
  559. Don't think of "::" as adding "10" to the front of "[  20,  30,  40  ]".   It
  560. really  definines  a  new  list  "[  10,  20, 30, 40 ]" in terms of two other
  561. objects without changing their meaning, rather in the same way that "1  +  3"
  562. defines a new value of "4" without changing the meaning of "1" or "3".
  563.  
  564.  
  565. The other standard list function is "nil",  which  defines  a  list  with  no
  566. elements  in  it.  We can represent every list by an expression consisting of
  567. applications of "::" and "nil".  When we write an expression like:
  568.  
  569.  
  570.      [ a + 1, b - 2, c * d ]
  571.  
  572.  
  573. it's considered to be a shorthand way of writing:
  574.  
  575.  
  576.      a + 1 :: ( b - 2 :: ( c * d :: nil ) )
  577.  
  578.  
  579. There's also a shorthand way of writing lists of characters.   The  following
  580. three expressions are all equivalent:
  581.  
  582.  
  583.      "cat"        [ 'c', 'a', 't' ]        'c' :: ( 'a' :: ( 't' :: nil ) )
  584.  
  585.  
  586.  
  587.  
  588.  
  589.  
  590.  
  591. Hope Tutorial                                                          Page 9
  592.  
  593.  
  594.  
  595.  
  596.  
  597.  
  598.  
  599.  
  600.  
  601.  
  602. When the result of a Hope program is a list, it's always printed out  in  the
  603. concise  bracketed  notation;  if  it's a list of characters, it's printed in
  604. quotes.
  605.  
  606.  
  607. Every data type in Hope is defined by a set of primitive functions like  "::"
  608. and  "nil".   They're  called  constructor  functions,  and aren't defined by
  609. recursion equations.  When we defined a  tuple,  we  were  actually  using  a
  610. standard  constructor  called  "," (read as 'comma').  Later on we'll see how
  611. constructors are defined for other types of data.
  612.  
  613.  
  614.  
  615.  
  616.  
  617.                          Functions that define lists
  618.  
  619.  
  620. If we wanted to write a Pascal program to print the first n  natural  numbers
  621. in  descending order we'd probably write a loop that printed one value out on
  622. each iteration, such as:
  623.  
  624.  
  625.      for i := n downto 1 do write ( i ) ;
  626.  
  627.  
  628. In Hope we write one expression that defines all the values at  once,  rather
  629. like we did for "mult":
  630.  
  631.  
  632.      dec nats : num -> list ( num ) ;
  633.      --- nats ( n ) <= if n = 0 then nil else n :: nats ( n-1 ) ;
  634.  
  635.  
  636. "nil" is useful for writing the  base  case  of  a  recursive  function  that
  637. defines a list.  If we try the function at the terminal by typing:
  638.  
  639.  
  640.      nats ( 10 ) ;
  641.      [ 10,9,8,7,6,5,4,3,2,1 ] : list ( num )
  642.  
  643.  
  644. we can see that the numbers are in descending order because that's the way we
  645. arranged  them  in the list, and not because they were defined in that order.
  646. The values in the expression defining the list are  treated  as  though  they
  647. were  all generated at the same time.  On the ALICE machine they actually are
  648. generated at the same time.
  649.  
  650.  
  651.  
  652.  
  653.  
  654.  
  655.  
  656.  
  657. Hope Tutorial                                                         Page 10
  658.  
  659.  
  660.  
  661.  
  662.  
  663.  
  664.  
  665.  
  666.  
  667.  
  668. To get the results of a Hope program in the right order, we must put them  in
  669. the  right  place  in  the  final data structure.  If we want the list of the
  670. numbers "n" through "1" in the opposite order we can't write  the  definition
  671. as:
  672.  
  673.  
  674.      ...  else nats ( n-1 ) :: n ;
  675.  
  676.  
  677. because the argument types for "::" are the wrong way round.  We need to  use
  678. a  another  built-in  operation "<>" (read as 'append') that concatenates two
  679. lists.  The definition will then look like this:
  680.  
  681.  
  682.      --- nats ( n ) <= if n = 0 then nil else nats ( n-1 ) <> [ n ] ;
  683.  
  684.  
  685. We put "n" in brackets to make it into  a  (single-item)  list  because  "<>"
  686. expects  both  its arguments to be lists.  We could also have written "( n ::
  687. nil )" instead of "[ n ]".
  688.  
  689.  
  690.  
  691.  
  692.  
  693.  
  694.                         Data structures as parameters
  695.  
  696.  
  697. Suppose we have a list of integers and we want to write a function to add  up
  698. all its elements.  The declaration will look like this:
  699.  
  700.  
  701.      dec sumlist : list ( num ) -> num ;
  702.  
  703.  
  704. We need to refer to the individual elements of the actual  parameter  in  the
  705. equations  defining  "sumlist".  We do this using an equation whose left-hand
  706. side looks like this:
  707.  
  708.  
  709.      --- sumlist ( x :: y ) ...
  710.  
  711.  
  712.  
  713.  
  714.  
  715.  
  716.  
  717.  
  718.  
  719.  
  720.  
  721.  
  722.  
  723. Hope Tutorial                                                         Page 11
  724.  
  725.  
  726.  
  727.  
  728.  
  729.  
  730.  
  731.  
  732.  
  733.  
  734. This is an expression involving  list  constructors  and  corresponds  to  an
  735. actual  parameter that's a list.  "x" and "y" are formal parameters, but they
  736. now name individual parts of the actual parameter value.  In  an  application
  737. of "sumlist" like:
  738.  
  739.  
  740.      sumlist ( [ 1, 2, 3 ] )
  741.  
  742.  
  743. the actual parameter will be 'dismantled' so that "x" names the value "1" and
  744. "y" names the value "[ 2, 3 ]".  The complete equation will be:
  745.  
  746.  
  747.      --- sumlist ( x :: y ) <= x + sumlist ( y ) ;
  748.  
  749.  
  750. Notice there's no base case test.  As we might expect, it's the  empty  list,
  751. but  we  can't test for it directly in the equation because there's no formal
  752. parameter that  refers  to  the  whole  list.   In  fact,  if  we  write  the
  753. application:
  754.  
  755.  
  756.      sumlist ( nil )
  757.  
  758.  
  759. we'll get an error message because we  can't  dismantle  "nil"  to  find  the
  760. values  of  "x"  and  "y".  We must cover this case separately using a second
  761. recursion equation:
  762.  
  763.  
  764.      --- sumlist ( nil ) <= 0 ;
  765.  
  766.  
  767. The two equations can be given in either order.  When "sumlist"  is  applied,
  768. the  actual  parameter is examined to see which constructor function was used
  769. to define it.  If the  actual  parameter  is  a  non-empty  list,  the  first
  770. equation  is  used,  because  non-empty  lists  are  defined  using  the "::"
  771. constructor.  The first number in the list gets named "x" and  the  remaining
  772. list  "y".  If the actual parameter is the empty list, the second equation is
  773. be used because empty lists are defined using the constructor "nil".
  774.  
  775.  
  776.  
  777.  
  778.  
  779.  
  780.  
  781.  
  782.  
  783.  
  784.  
  785.  
  786.  
  787.  
  788.  
  789. Hope Tutorial                                                         Page 12
  790.  
  791.  
  792.  
  793.  
  794.  
  795.  
  796.  
  797.  
  798.  
  799.  
  800.                               Pattern-matching
  801.  
  802. An expression composed of constructors appearing on the left-hand side  of  a
  803. recursion  equation  is  called  a  pattern.   Selecting  the right recursion
  804. equation and dismantling the actual parameter to name  its  parts  is  called
  805. pattern-matching.   When  you  write  a  function,  you must give a recursion
  806. equation for each possible constructor defining the argument type.
  807.  
  808.  
  809. Sometimes we don't need to dismantle the actual parameter, and we can  use  a
  810. formal  parameter  in the pattern that matches the whole object, irrespective
  811. of what constructors were used to define it.  As an example, let's see how we
  812. could  define  our  own  function  to concatenate two lists like the built-in
  813. operation "<>":
  814.  
  815.  
  816.      infix cat : 4 ;
  817.      dec cat : list( num ) # list( num ) -> list ( num ) ;
  818.      --- ( h :: t ) cat l <= h :: ( t cat l ) ;
  819.      --- nil cat l <= l ;
  820.  
  821.  
  822. The first list parameter is matched by the pattern "( h :: t )" so  that  its
  823. first  item  (the 'head") and the remaining list (the 'tail') can be referred
  824. to separately on the right-hand side.  The second recursion  equation  covers
  825. the  case when the first list is empty.  The second list parameter is matched
  826. by the pattern "l" whether it's empty or not.
  827.  
  828.  
  829. As well as writing enough recursion equations to satisfy  all  the  parameter
  830. constructors,  we  must  also be careful not to write sets of equations where
  831. more than one pattern might match the actual parameters, because  that  would
  832. be ambiguous.
  833.  
  834.  
  835. We can write patterns to match arguments that are  tuples  in  the  same  way
  836. using  the tuple constructor ",".  When we wrote "mult ( x, y )" you probably
  837. thought the parentheses and the comma were something to do with the  function
  838. application.   In fact, we were constructing a tuple and the parentheses were
  839. only needed because "," has a low priority.  Hope  treats  all  functions  as
  840. having  only  one  argument.  This can be a tuple when you want the effect of
  841. several arguments.  Without parentheses,
  842.  
  843.  
  844.      mult x, y
  845.  
  846.  
  847. would be interpreted as:
  848.  
  849.  
  850.      ( mult ( x ), y )
  851.  
  852.  
  853.  
  854.  
  855. Hope Tutorial                                                         Page 13
  856.  
  857.  
  858.  
  859.  
  860.  
  861.  
  862.  
  863.  
  864.  
  865.  
  866. A recursion equation with the left-hand side:
  867.  
  868.  
  869.      --- mult ( x, y ) <= ...
  870.  
  871.  
  872. is just a pattern-match on a tuple.  The first item in the tuple  gets  named
  873. "x" and the second one "y".
  874.  
  875.  
  876. We can also use pattern-matching on "num" parameters.  These are  defined  by
  877. two  constructors called "succ" and "0".  "succ" defines a number in terms of
  878. the next lower one.  "0" has no arguments and defines the value zero.  Surely
  879. "0"  is  a  value,  not  a  function? Well, we're already used to thinking of
  880. function applications as  another  way  of  writing  values,  so  it's  quite
  881. consistent  to  think  of "0" as a function application.  Here's a version of
  882. "mult" that uses pattern-matching to identify the base case:
  883.  
  884.  
  885.      infix mult : 8 ;
  886.      dec mult : num # num -> num ;
  887.      --- x mult 0          <= 0 ;
  888.      --- x mult succ ( y ) <= ( x mult y ) + x ;
  889.  
  890.  
  891. We can read "succ ( y )" as 'the successor of some  number  that  we'll  call
  892. "y"'.  Instead of naming the actual parameter "y" like we did in the original
  893. version of "mult", we're naming its predecessor.
  894.  
  895.  
  896.  
  897.  
  898.  
  899.  
  900.  
  901.  
  902.  
  903.  
  904.  
  905.  
  906.  
  907.  
  908.  
  909.  
  910.  
  911.  
  912.  
  913.  
  914.  
  915.  
  916.  
  917.  
  918.  
  919.  
  920.  
  921. Hope Tutorial                                                         Page 14
  922.  
  923.  
  924.  
  925.  
  926.  
  927.  
  928.  
  929.  
  930.  
  931.  
  932.                            Simplifying expressions
  933.  
  934. In Pascal programs we can simplify complex  expressions  by  removing  common
  935. sub-expressions and evaluating them separately.  Instead of:
  936.  
  937.  
  938.      writeln ( ( x + y ) * ( x + y ) ) ;
  939.  
  940.  
  941. we would probably write:
  942.  
  943.  
  944.      z := x + y ; writeln ( z * z ) ;
  945.  
  946.  
  947. which  is  clearer  and  more  efficient.   Hope  programs  consist  only  of
  948. expressions  and  it's  even  more important to simplify them.  We do this by
  949. using a qualified expression:
  950.  
  951.  
  952.      let z == x + y in z * z ;
  953.  
  954.  
  955. This looks like an assignment, but it isn't.  "==" is read as 'is defined as'
  956. and "z" is local to the expression following the "in".  If we write something
  957. like:
  958.  
  959.  
  960.      let z == z + 1 in z * z ;
  961.  
  962.  
  963. we're actually introducing a new variable "z" to use in the sub-expression "z
  964. * z".  It hides the original one in the sub-expression "z + 1".
  965.  
  966.  
  967. There's a second form of qualified expression for  people  who  like  to  use
  968. variables first and define their meanings later.  It looks like this:
  969.  
  970.  
  971.      z * z where z == x + y ;
  972.  
  973.  
  974. The result of the qualified expression is the same whether we define it using
  975. let  or where.  "x + y" is evaluated first, and its value is used in the main
  976. expression.
  977.  
  978.  
  979.  
  980.  
  981.  
  982.  
  983.  
  984.  
  985.  
  986.  
  987. Hope Tutorial                                                         Page 15
  988.  
  989.  
  990.  
  991.  
  992.  
  993.  
  994.  
  995.  
  996.  
  997.  
  998. The qualifying expression will often be a function application that defines a
  999. data  structure.   If  we  want  to  name  part of the structure we can use a
  1000. pattern on the left-hand side of the "==" symbol:
  1001.  
  1002.  
  1003.      dec time12 : num -> num # num ;
  1004.      --- time12 ( s ) <= ( if h > 12 then h-12 else h, m ) where
  1005.                     ( h, m, s ) == time24 ( s ) ;
  1006.  
  1007.  
  1008. We'll use this construction most often when we write recursive functions that
  1009. define  tuples.   Here's a typical example.  Suppose we want to form a string
  1010. of words from a sentence.  For simplicity a word is taken to be any  sequence
  1011. of  characters,  and  words  are  separated  in the sentence by any number of
  1012. blanks.  The sentence and a single word will be of type "list ( char  )"  and
  1013. the final sequence of words a "list ( list ( char ) )".
  1014.  
  1015.  
  1016. It's fairly straightforward to obtain the first word.  Here's a function that
  1017. does it:
  1018.  
  1019.  
  1020.      dec firsttry : list ( char ) -> list ( char ) ;
  1021.      --- firsttry ( nil )    <= nil ;
  1022.      --- firsttry ( c :: s ) <= if c = ' '
  1023.                              then nil
  1024.                              else c :: firsttry ( s ) ;
  1025.  
  1026.  
  1027. One of the nice features of Hope is that we can type in  and  print  out  any
  1028. kind  of  value,  so  it's  easy to check out the individual functions of our
  1029. program separately.  If we test "firsttry" we'll see:
  1030.  
  1031.  
  1032.      firsttry ( "You may hunt it with forks and Hope" ) ;
  1033.      "You" : list ( char )
  1034.  
  1035.  
  1036. But there's a problem here because we're  going  to  need  the  rest  of  the
  1037. sentence if we're to find the remaining words.  We must arrange that that the
  1038. function returns the remaining list as well as the first word.  This is where
  1039. tuples come in:
  1040.  
  1041.  
  1042.      dec firstword : list ( char ) -> list ( char ) # list ( char ) ;
  1043.      --- firstword ( nil )    <= ( nil, nil ) ;
  1044.      --- firstword ( c :: s ) <= if c = ' '
  1045.                               then ( nil, s )
  1046.                               else ( ( c :: w, r ) where
  1047.                                      ( w, r ) == firstword ( s ) ) ;
  1048.  
  1049.  
  1050.  
  1051.  
  1052.  
  1053. Hope Tutorial                                                         Page 16
  1054.  
  1055.  
  1056.  
  1057.  
  1058.  
  1059.  
  1060.  
  1061.  
  1062.  
  1063.  
  1064. The  qualified  expression  is  parenthesised  so  it  only  applies  to  the
  1065. expression after the else, otherwise we'll evaluate "firstword" recursivly as
  1066. long as the sentence is non-empty, even if it  starts  with  a  blank.   This
  1067. version of the function produces:
  1068.  
  1069.  
  1070.      firstword ( "Hope springs eternal ..." ) ;
  1071.      ( "Hope","springs eternal ..." ) : ( list ( char ) # list ( char ) )
  1072.  
  1073.  
  1074. We can use this to define a function to split the sentence into a list of its
  1075. individual words:
  1076.  
  1077.  
  1078.      dec wordlist : list ( char ) -> list ( list ( char ) ) ;
  1079.      --- wordlist ( nil )    <= nil ;
  1080.      --- wordlist ( c :: s ) <= if c = ' '
  1081.                              then wordlist( s )
  1082.                              else ( w :: wordlist ( r ) where
  1083.                                   ( w, r ) == firstword ( c :: s ) ) ;
  1084.  
  1085.  
  1086. which we can test by typing an application at the terminal:
  1087.  
  1088.  
  1089.      wordlist ( "  While there's life there's Hope  " ) ;
  1090.      [ "While","there's","life","there's","Hope" ] : list ( list ( char ) )
  1091.  
  1092.  
  1093.  
  1094.  
  1095.  
  1096.  
  1097.                                    Review
  1098.  
  1099. So far we've concentrated on features of Hope that have something  in  common
  1100. with  traditional  languages  such  as  Pascal,  but  without  many  of their
  1101. limitations, such as fixed-size data structures.  We've also been  introduced
  1102. to  the  functional style of programming where programs are no longer recipes
  1103. for action, but just definitions of data objects.
  1104.  
  1105.  
  1106. Now we'll introduce features of Hope that lift it onto a much higher level of
  1107. expressive  power,  and  let  us  write  programs that are not only extremely
  1108. powerful and concise, but that can be checked for correctness at compile-time
  1109. and mechanically transformed into more efficient versions.
  1110.  
  1111.  
  1112.  
  1113.  
  1114.  
  1115.  
  1116.  
  1117.  
  1118.  
  1119. Hope Tutorial                                                         Page 17
  1120.  
  1121.  
  1122.  
  1123.  
  1124.  
  1125.  
  1126.  
  1127.  
  1128.  
  1129.  
  1130.                        Making functions more powerful
  1131.  
  1132. The Hope compiler can spot many common kinds of error by checking  the  types
  1133. of all objects in expressions.  This is harder than checking at run-time, but
  1134. more efficient and  saves  the  embarrassment  of  discovering  an  error  at
  1135. run-time  in  a  rarely-executed  branch of the air traffic control system we
  1136. just wrote.
  1137.  
  1138.  
  1139. However, strict type-checking can be a nuisance if we want  to  perform  some
  1140. operation  that doesn't depend on the type of the data.  Try writing a Pascal
  1141. procedure to reverse an array of either 10  integers  or  10  characters  and
  1142. you'll see what I mean.
  1143.  
  1144.  
  1145. Hope avoids this kind of restriction by allowing a  function  to  operate  on
  1146. more  than  one type of object.  We've already used the standard constructors
  1147. "::" and "nil" to define a "list ( num )", a "list ( char )" and  a  "list  (
  1148. list  (  char  ) )".  The standard equality function "=" will compare any two
  1149. objects  of  the  same  type.   Functions  with  this  property  are   called
  1150. polymorphic.  Pascal's built-in functions "abs" and "sqr" and operations like
  1151. ">" and "=" are polymorphic in a primitive kind of way.
  1152.  
  1153.  
  1154. We can define our own polymorphic functions in Hope.  The function  "cat"  we
  1155. defined  above will concatenate lists of numbers, but we can use it for lists
  1156. containing any type of object.  To  do  this  we  first  declare  a  kind  of
  1157. 'universal  type'  called a type variable.  We use this in the declaration of
  1158. "cat" where it stands for any actual type:
  1159.  
  1160.  
  1161.      typevar alpha ;
  1162.      infix cat : 8 ;
  1163.      dec cat : list ( alpha ) # list ( alpha ) -> list ( alpha ) ;
  1164.  
  1165.  
  1166. This says that "cat" has two parameters that are lists and  defines  a  list,
  1167. but  it  doesn't  say  what  kind of object is in the list.  However, "alpha"
  1168. always stands for the same type throughout a given declaration,  so  all  the
  1169. lists must contain the same type of object.  The expressions:
  1170.  
  1171.  
  1172.      [ 1,2,3 ]  cat  [ 4,5,6 ]         and         "123"  cat  "456"
  1173.  
  1174.  
  1175. are correctly-typed applications of "cat" and define a "list ( num )"  and  a
  1176. "list ( char )" respectively, while the expression:
  1177.  
  1178.  
  1179.      [ 1,2,3 ]  cat  "456"
  1180.  
  1181.  
  1182.  
  1183.  
  1184.  
  1185. Hope Tutorial                                                         Page 18
  1186.  
  1187.  
  1188.  
  1189.  
  1190.  
  1191.  
  1192.  
  1193.  
  1194.  
  1195.  
  1196. isn't because "alpha" can't be  interpreted  as  two  different  types.   The
  1197. interpretation  of  a  type variable is local to a declaration so it can have
  1198. different interpretations in other declarations without confusion.
  1199.  
  1200.  
  1201. Of course it only makes sense for a function to be polymorphic as long as the
  1202. equations defining it don't make any assumptions about types.  In the case of
  1203. "cat" the  definition  uses  only  "::"  and  "nil",  which  are  polymorphic
  1204. themselves.  However, a function like "sumlist" uses "+" and can only be used
  1205. with lists of numbers as parameters.
  1206.  
  1207.  
  1208.  
  1209.  
  1210.  
  1211.  
  1212.                         Defining your own data types
  1213.  
  1214. Tuples and lists are quite powerful, but for more sophisticated applications,
  1215. we'll need to define our own types.  User-defined types make programs clearer
  1216. and help the type-checker to help the programmer.  We introduce  a  new  data
  1217. type in a data declaration:
  1218.  
  1219.  
  1220.      data vague == yes ++ no ++ maybe ;
  1221.  
  1222.  
  1223. "data" is a reserved word and "vague" is the name of the new type.   "=="  is
  1224. read  as  'is  defined as' and "++" is read as 'or'.  "yes", "no" and "maybe"
  1225. are the names for the constructor functions of the  new  type.   We  can  now
  1226. write function definitions that use these constructors in patterns:
  1227.  
  1228.  
  1229.      dec evade : vague -> vague ;
  1230.      --- evade ( yes )   <= maybe ;
  1231.      --- evade ( maybe ) <= no ;
  1232.  
  1233.  
  1234. The constructors can be parameterised with any type of object, including  the
  1235. type that's being defined.  We can define types like lists, whose objects are
  1236. of unlimited size using this kind of recursive definition.   As  an  example,
  1237. here's a user-defined binary tree that can contain numbers as its leaves:
  1238.  
  1239.  
  1240.      data tree == empty ++ tip ( num ) ++ node ( tree # tree ) ;
  1241.  
  1242.  
  1243.  
  1244.  
  1245.  
  1246.  
  1247.  
  1248.  
  1249.  
  1250.  
  1251. Hope Tutorial                                                         Page 19
  1252.  
  1253.  
  1254.  
  1255.  
  1256.  
  1257.  
  1258.  
  1259.  
  1260.  
  1261.  
  1262. There are three constructors.  "empty" has no parameters and defines  a  tree
  1263. with  nothing  in  it.   "tip"  defines  a tree in terms of a single num, and
  1264. "node" defines a tree in terms of two other trees.  Here's a typical tree:
  1265.  
  1266.  
  1267.                                       .
  1268.                                      / \
  1269.                                     /   \
  1270.                                 ___/     \___
  1271.                        ________/             \________
  1272.                       /       /               \       \
  1273.                      /       /\               /\       \
  1274.                     1       /  \             /  \       \
  1275.                            /    \           /    \       5
  1276.                           2      3         .      4
  1277.  
  1278.  
  1279. Here's an example of a function that manipulates trees.  It returns  the  sum
  1280. of all the numbers contained in one:
  1281.  
  1282.  
  1283.      dec sumtree : tree -> num ;
  1284.      --- sumtree ( empty )         <= 0 ;
  1285.      --- sumtree ( tip ( n ) )     <= n ;
  1286.      --- sumtree ( node ( l, r ) ) <= sumtree ( l ) + sumtree ( r ) ;
  1287.  
  1288.  
  1289. Unfortunately there's no shorthand for writing tree constants like  there  is
  1290. for  list  constants,  so  we've  got  to  write  them out the long way using
  1291. constructors.  If we want to use "sumtree" to add up all the numbers  in  the
  1292. example tree above, we must type in the expression:
  1293.  
  1294.  
  1295.      sumtree ( node ( node ( tip ( 1 ),
  1296.                         node ( tip ( 2 ),
  1297.                                tip ( 3 ) ) ),
  1298.                  node ( node ( empty,
  1299.                                tip ( 4 ) ),
  1300.                         tip ( 5 ) ) ) ) ;
  1301.  
  1302.  
  1303. This isn't really a drawback, because programs that manipulate  complex  data
  1304. structures  like  trees  will  generally  define  them using other functions.
  1305. However, it's very useful to be able  to  type  any  kind  of  constant  data
  1306. structure at the terminal when we're checking out an individual function like
  1307. "sumtree".  When we want to test a Pascal program piecemeal, we usually  have
  1308. to write elaborate test harnesses or stubs to generate test data.
  1309.  
  1310.  
  1311.  
  1312.  
  1313.  
  1314.  
  1315.  
  1316.  
  1317. Hope Tutorial                                                         Page 20
  1318.  
  1319.  
  1320.  
  1321.  
  1322.  
  1323.  
  1324.  
  1325.  
  1326.  
  1327.  
  1328.                           Making data more abstract
  1329.  
  1330. The identifier "list" isn't really a Hope data  type.   It's  called  a  type
  1331. constructor  and  must  be  parameterised  with  an  actual  type  before  it
  1332. represents one.  We did this every time we declared a "list (  num  )"  or  a
  1333. "list  (  char  )".  The parameter can also be a user-defined type, as with a
  1334. "list ( tree )" or even a type variable as in "list ( alpha )", which defines
  1335. a  polymorphic  data  type.   Constructing  new  data  types  like  this is a
  1336. compile-time operation  by  the  way,  and  you  shouldn't  confuse  it  with
  1337. constructing new data values, which is a run-time operation.
  1338.  
  1339.  
  1340. You can define your own polymorphic data types.   Here's  a  version  of  the
  1341. binary tree we defined earlier that can have any type of value in its leaves:
  1342.  
  1343.  
  1344.      data tree ( alpha ) == empty ++
  1345.                        tip ( alpha ) ++
  1346.                        node ( tree ( alpha ) # tree ( alpha ) ) ;
  1347.  
  1348.  
  1349. Once again, "alpha" is taken to be the same type throughout one instance of a
  1350. tree.  If it's a number, then all references to "tree ( alpha )" are taken as
  1351. references to "tree ( num )".
  1352.  
  1353.  
  1354. We can define polymorphic functions that operate on trees containing any type
  1355. of  object, because tree constructors are now polymorphic.  Here's a function
  1356. to 'flatten' a binary tree into a list of the same type of object:
  1357.  
  1358.  
  1359.      dec flatten : tree ( alpha ) -> list ( alpha ) ;
  1360.      --- flatten ( empty )         <= nil ;
  1361.      --- flatten ( tip ( x ) )     <= x :: nil ;
  1362.      --- flatten ( node ( x, y ) ) <= flatten ( x ) <> flatten ( y ) ;
  1363.  
  1364.  
  1365.  
  1366.  
  1367.  
  1368.  
  1369.  
  1370.  
  1371.  
  1372.  
  1373.  
  1374.  
  1375.  
  1376.  
  1377.  
  1378.  
  1379.  
  1380.  
  1381.  
  1382.  
  1383. Hope Tutorial                                                         Page 21
  1384.  
  1385.  
  1386.  
  1387.  
  1388.  
  1389.  
  1390.  
  1391.  
  1392.  
  1393.  
  1394. We can demonstrate it on various kinds of tree:
  1395.  
  1396.  
  1397.      flatten( node ( tip ( 1 ), node ( tip ( 2 ), tip ( 3 ) ) ) ) ;
  1398.      [ 1, 2, 3 ] : list ( num )
  1399.  
  1400.  
  1401.      flatten( node ( tip ( "one" ),
  1402.                 node ( tip ( "two" ),
  1403.                        tip ( "three" ) ) ) ) ;
  1404.      [ "one","two","three" ] : list ( list ( char ) )
  1405.  
  1406.  
  1407.      flatten( node ( tip ( tip ( 'a' ) ),
  1408.                 node ( tip ( empty ),
  1409.                        tip ( node ( tip ( 'c' ),
  1410.                                     empty ) ) ) ) ) ;
  1411.      [ tip ( 'a' ),empty,node ( tip ( 'c' ), empty) ] : list ( tree ( char ) )
  1412.  
  1413.  
  1414. Notice how the type-checker may need to go through  several  levels  of  data
  1415. types to deduce the type of the result.
  1416.  
  1417.  
  1418.  
  1419.  
  1420.  
  1421.  
  1422.  
  1423.  
  1424.  
  1425.  
  1426.  
  1427.  
  1428.  
  1429.  
  1430.  
  1431.  
  1432.  
  1433.  
  1434.  
  1435.  
  1436.  
  1437.  
  1438.  
  1439.  
  1440.  
  1441.  
  1442.  
  1443.  
  1444.  
  1445.  
  1446.  
  1447.  
  1448.  
  1449. Hope Tutorial                                                         Page 22
  1450.  
  1451.  
  1452.  
  1453.  
  1454.  
  1455.  
  1456.  
  1457.  
  1458.  
  1459.  
  1460.                          Even more concise programs
  1461.  
  1462. The importance of polymorphic types and functions is that they let  us  write
  1463. shorter,  clearer programs.  It's similar to the way Pascal procedures let us
  1464. use the same code  to  operate  on  different  data  values,  but  much  more
  1465. powerful.   We  can write a single Hope function to reverse a list of numbers
  1466. or characters, where we'd need to write two identical Pascal  subroutines  to
  1467. reverse an array of integers and an array of characters.
  1468.  
  1469. We can use polymorphic functions  whenever  we're  concerned  only  with  the
  1470. arrangement  of  the  objects  in a data structure and not with their values.
  1471. Sometimes, we'll want to apply some function to the primitive data  items  in
  1472. the  structure.   Here's  a  function that uses a second function "square" to
  1473. define a "list (num)" whose elements are the squares of another "list (num)":
  1474.  
  1475.  
  1476.      dec square : num -> num ;
  1477.      --- square ( n ) <= n * n ;
  1478.  
  1479.  
  1480.      dec squarelist : list ( num ) -> list ( num ) ;
  1481.      --- squarelist ( nil )    <= nil ;
  1482.      --- squarelist ( n :: l ) <= square ( n ) :: squarelist ( l ) ;
  1483.  
  1484.  
  1485. Every time we write a function to process every  element  of  a  list,  we'll
  1486. write  something  almost  identical  to  "squarelist".   Here's a function to
  1487. define a list of factorials:
  1488.  
  1489.  
  1490.      dec fact : num -> num ;
  1491.      --- fact ( 0 )          <= 1 ;
  1492.      --- fact ( succ ( n ) ) <= succ ( n ) * fact ( n ) ;
  1493.  
  1494.  
  1495.      dec factlist : list ( num ) -> list ( num ) ;
  1496.      --- factlist ( nil )    <= nil ;
  1497.      --- factlist ( n :: l ) <= fact ( n ) :: factlist ( l ) ;
  1498.  
  1499.  
  1500. "factlist" has exactly the same 'shape'  as  "squarelist",  it  just  applies
  1501. "fact"  instead of "square" and then applies itself recursively.  Values that
  1502. differ between applications are usually supplied as actual  parameters.  Hope
  1503. treats  functions  as  data objects, so we can do this in a perfectly natural
  1504. way.  A function that can take another function as  an  actual  parameter  is
  1505. called  a higher-order function.  When we declare it we must give the type of
  1506. formal parameter standing for the function in the usual way.  The declaration
  1507. of "fact" tells us that it's:
  1508.  
  1509.      num -> num
  1510.  
  1511.  
  1512.  
  1513.  
  1514.  
  1515. Hope Tutorial                                                         Page 23
  1516.  
  1517.  
  1518.  
  1519.  
  1520.  
  1521.  
  1522.  
  1523.  
  1524.  
  1525.  
  1526. Read this as 'a function mapping numbers to numbers'.  Now let's see  how  we
  1527. can  use  this  idea to write "factlist" and "squarelist" as a single higher-
  1528. order function.  The new function needs two parameters,  the  original  list,
  1529. and the function that's applied inside it.  Its declaration will be:
  1530.  
  1531.  
  1532.      dec alllist : list ( num ) # ( num -> num ) -> list ( num ) ;
  1533.  
  1534.  
  1535. The 'shape' of "alllist" will be the same as "factlist" and "squarelist", but
  1536. the  function  we  apply  to  each  element  of  the  list will be the formal
  1537. parameter:
  1538.  
  1539.  
  1540.      --- alllist ( nil, f )    <= nil ;
  1541.      --- alllist ( n :: l, f ) <= f ( n ) :: alllist ( l, f ) ;
  1542.  
  1543.  
  1544. We use "alllist" like this:
  1545.  
  1546.  
  1547.      alllist ( [ 2,4,6 ], square ) ;
  1548.      [ 4,16,36 ] : list ( num )
  1549.  
  1550.  
  1551.      alllist ( [ 2,4,6 ], fact ) ;
  1552.      [ 2,24,720 ] : list ( num )
  1553.  
  1554.  
  1555. Notice that there's  no  argument  list  after  "square"  or  "fact"  in  the
  1556. application  of  "alllist",  so  this  construction  won't  be  confused with
  1557. functional composition.  "fact( 3 )" represents a function  application,  but
  1558. "fact" by itself represents the unevaluated function.
  1559.  
  1560.  
  1561. Higher-order functions can also be polymorphic.  We  can  use  this  idea  to
  1562. write  a  more  powerful  version  of  "alllist" that will apply an arbitrary
  1563. function to every element of a list  of  objects  of  arbitrary  type.   This
  1564. version of the function is usually known as "map":
  1565.  
  1566.  
  1567.      typevar alpha, beta ;
  1568.      dec map : list ( alpha ) # ( alpha -> beta ) -> list ( beta ) ;
  1569.      --- map ( nil, f ) <= nil ;
  1570.      --- map ( n :: l, f ) <= f ( n ) :: map ( l, f ) ;
  1571.  
  1572.  
  1573. The definition now uses two type variables  "alpha"  and  "beta".   Each  one
  1574. represents the same actual type throughout one instance of "map", but the two
  1575. types can be different.  This means we can use any function that maps  alphas
  1576. to betas to generate a list of betas from any list of alphas.
  1577.  
  1578.  
  1579.  
  1580.  
  1581. Hope Tutorial                                                         Page 24
  1582.  
  1583.  
  1584.  
  1585.  
  1586.  
  1587.  
  1588.  
  1589.  
  1590.  
  1591.  
  1592. The actual types aren't restricted to scalars, which makes "map" rather  more
  1593. powerful  than we might realise at first sight.  Suppose we've got a suitably
  1594. polymorphic function that finds the length of a list:
  1595.  
  1596.  
  1597.      typevar gamma ;
  1598.      dec length : list ( gamma ) -> num ;
  1599.      --- length ( nil )    <= 0 ;
  1600.      --- length ( n :: l ) <= 1 + length ( l ) ;
  1601.  
  1602.  
  1603.      length ( [ 2,4,6,8 ] ) + length ( "cat" ) ;
  1604.      7 : num
  1605.  
  1606.  
  1607. We can use "map" to apply "length" to  every  element  of  a  list  of  words
  1608. defined by "wordlist":
  1609.  
  1610.  
  1611.      map ( wordlist ( "The form remains, the function never dies" ), length )
  1612.      [ 3,4,8,3,8,5,4 ] : list ( num ) ;
  1613.  
  1614.  
  1615. In this example "alpha" is taken to be a "list ( char )" and "beta" to  be  a
  1616. num, so the type of the function must be "( list ( char ) -> num )". "length"
  1617. fits the bill if "gamma" is taken to be a character.
  1618.  
  1619.  
  1620.  
  1621.  
  1622.  
  1623.  
  1624.  
  1625.  
  1626.  
  1627.  
  1628.  
  1629.  
  1630.  
  1631.  
  1632.  
  1633.  
  1634.  
  1635.  
  1636.  
  1637.  
  1638.  
  1639.  
  1640.  
  1641.  
  1642.  
  1643.  
  1644.  
  1645.  
  1646.  
  1647. Hope Tutorial                                                         Page 25
  1648.  
  1649.  
  1650.  
  1651.  
  1652.  
  1653.  
  1654.  
  1655.  
  1656.  
  1657.  
  1658.                         Common patterns of recursion
  1659.  
  1660. "map" is powerful because it sums up a pattern of  recursion  that  turns  up
  1661. frequently  in  Hope  programs.   We  can  see  another common pattern in the
  1662. function "length" used above.  Here's another example of the same pattern:
  1663.  
  1664.  
  1665.      dec sum : list ( num ) -> num ;
  1666.      --- sum ( nil )    <= 0 ;
  1667.      --- sum ( n :: l ) <= n + sum ( l ) ;
  1668.  
  1669.  
  1670. The underlying pattern consists of processing each element in  the  list  and
  1671. accumulating  a  single  value that forms the result.  In "sum", each element
  1672. contributes its value to the final result.  In "length" the  contribution  is
  1673. always  "1" irrespective of the type or value of the element, but the pattern
  1674. is identical.  Functions that display this pattern are of type:
  1675.  
  1676.  
  1677.      ( list ( alpha ) -> beta )
  1678.  
  1679.  
  1680. In the function definition, the equation for a non-empty list parameter  will
  1681. specify  an  operation  whose result is a "beta".  This is "+" in the case of
  1682. "length" and "sum".  One argument of the operation will be a list element and
  1683. the  other  will be defined by a recursive call, so the type of the operation
  1684. needs to be:
  1685.  
  1686.  
  1687.      ( alpha # beta -> beta )
  1688.  
  1689.  
  1690. This operation differs between applications, so it  will  be  supplied  as  a
  1691. parameter.   Finally  we  need a parameter of type "beta" to specify the base
  1692. case result.  The final version of the function is usually known as "reduce".
  1693. In  the  following  definition,  the  symbol  "!" introduces a comment, which
  1694. finishes with another "!" or with a newline:
  1695.  
  1696.  
  1697.      dec reduce :   list ( alpha ) #            ! the input list          !
  1698.                     ( alpha # beta -> beta ) #  ! the reduction operation !
  1699.                     beta                        ! the base case result    !
  1700.                 ->  beta ;                      ! the result type         !
  1701.      --- reduce ( nil, f, b )    <= b ;
  1702.      --- reduce ( n :: l, f, b ) <= f ( n, reduce ( l, f, b ) ) ;
  1703.  
  1704.  
  1705.  
  1706.  
  1707.  
  1708.  
  1709.  
  1710.  
  1711.  
  1712.  
  1713. Hope Tutorial                                                         Page 26
  1714.  
  1715.  
  1716.  
  1717.  
  1718.  
  1719.  
  1720.  
  1721.  
  1722.  
  1723.  
  1724. To use "reduce" as a replacement for "sum" we'll need to supply the  standard
  1725. function "+" as an actual parameter.  We can do this if we prefix it with the
  1726. symbol nonop to tell the compiler we  don't  want  to  use  it  as  an  infix
  1727. operator:
  1728.  
  1729.  
  1730.      reduce ( [ 1,2,3 ], nonop +, 0 ) ;
  1731.      6 : num
  1732.  
  1733.  
  1734. When we use "reduce" as a replacement for "length", we're not  interested  in
  1735. the  first  argument  of  the  reduction  operation because we always add "1"
  1736. whatever the list element is.  This function ignores its first argument:
  1737.  
  1738.  
  1739.      dec addone : alpha # num -> num ;
  1740.      --- addone ( _ , n ) <= n + 1 ;
  1741.  
  1742.  
  1743. We use "_" to represent any argument we don't want to refer to.
  1744.  
  1745.  
  1746.      reduce ( "a map they could all understand", addone, 0 ) ;
  1747.      31 : num
  1748.  
  1749.  
  1750. Like "map", "reduce" is much more powerful than it first appears because  the
  1751. reduction  function needn't define a scalar.  Here's a candidate that inserts
  1752. an object into an ordered list of the same kind of object:
  1753.  
  1754.  
  1755.      dec insert : alpha # list ( alpha ) -> list ( alpha ) ;
  1756.      --- insert ( i, nil )    <= i :: nil ;
  1757.      --- insert ( i, h :: t ) <= if i < h
  1758.                               then i :: ( h :: t )
  1759.                               else h :: insert ( i, t ) ;
  1760.  
  1761.  
  1762.  
  1763.  
  1764.  
  1765.  
  1766.  
  1767.  
  1768.  
  1769.  
  1770.  
  1771.  
  1772.  
  1773.  
  1774.  
  1775.  
  1776.  
  1777.  
  1778.  
  1779. Hope Tutorial                                                         Page 27
  1780.  
  1781.  
  1782.  
  1783.  
  1784.  
  1785.  
  1786.  
  1787.  
  1788.  
  1789.  
  1790. Actually this isn't strictly polymorphic as its declaration suggests, because
  1791. it  uses  the  built-in  function "<", which is only defined over numbers and
  1792. characters, but it shows the kind of thing we can do.   When  we  use  it  to
  1793. reduce a list of characters:
  1794.  
  1795.  
  1796.      reduce ( "All sorts and conditions of men", insert, nil ) ;
  1797.      "     Aacddefiillmnnnnoooorssstt" : list ( char )
  1798.  
  1799.  
  1800. we can see that it actually sorts them.  The sorting method (insertion  sort)
  1801. isn't  very  efficient,  but  the  example  shows  something  of the power of
  1802. higher-order functions and of "reduce" in particular.  It's even possible  to
  1803. use  "reduce"  to get the effect of "map", but that's left as an exercise for
  1804. the reader as they say.
  1805.  
  1806.  
  1807. Of course "map" and "reduce" only work on "list ( alpha )" and we'll need  to
  1808. provide  different  versions  for our own structured data types.  This is the
  1809. preferred style of  Hope  programming,  because  it  makes  programs  largely
  1810. independent  of  the  'shape'  of  the  data  structures they use.  Here's an
  1811. alternative kind of binary tree that holds data at its nodes rather than  its
  1812. tips, and a reduce function for it:
  1813.  
  1814.  
  1815.      data tree ( alpha ) == empty ++
  1816.                        node ( tree ( alpha ) # alpha # tree ( alpha ) ) ;
  1817.  
  1818.      dec redtree : tree ( alpha ) #
  1819.               ( alpha # beta -> beta ) #
  1820.               beta -> beta ;
  1821.      --- redtree ( empty, f, b )            <= b ;
  1822.      --- redtree ( node ( l, v, r ), f, b ) <=
  1823.               redtree ( l, f, f ( v, redtree ( r, f, b ) ) ) ;
  1824.  
  1825.  
  1826. We can use this kind of tree to define a more  efficient  sort.   An  ordered
  1827. binary  tree  has  the  property  that  all  the  objects in its left subtree
  1828. logically precede the node object and all those  in  its  right  subtree  are
  1829. equal  to  the  node object or logically succeed it.  We can build an ordered
  1830. tree if we have a function to insert  new  objects  into  an  already-ordered
  1831. tree, such as:
  1832.  
  1833.  
  1834.      dec instree : alpha # tree ( alpha ) -> tree ( alpha ) ;
  1835.      --- instree ( i, empty ) <= node ( empty, i, empty ) ;
  1836.      --- instree ( i, node ( l, v, r ) ) <=
  1837.               if i < v
  1838.                then node ( instree ( i, l ), v, r )
  1839.                else node ( l, v, instree ( i, r ) ) ;
  1840.  
  1841.  
  1842.  
  1843.  
  1844.  
  1845. Hope Tutorial                                                         Page 28
  1846.  
  1847.  
  1848.  
  1849.  
  1850.  
  1851.  
  1852.  
  1853.  
  1854.  
  1855.  
  1856. We can sort a list by converting its elements  into  an  ordered  tree  using
  1857. "instree",  then  flattening the tree back into a list.  This is very easy to
  1858. specify using the two kinds of reduction we've defined:
  1859.  
  1860.  
  1861.      dec sort : list ( alpha ) -> list ( alpha ) ;
  1862.      --- sort ( l ) <= redtree( reduce ( l, instree, empty ), nonop ::, nil )
  1863.  
  1864.  
  1865.      sort ( "Mad dogs and Englishmen" ) ;
  1866.      "   EMaadddegghilmnnnoss" : list ( char )
  1867.  
  1868.  
  1869.  
  1870.  
  1871.  
  1872.  
  1873.                              Anonymous functions
  1874.  
  1875. When we used "map" and "reduce", we had to define extra functions like "fact"
  1876. and  "square" to pass in as paramteters.  This is a nuisance if we don't need
  1877. them anywhere else in the program and especially  if  they're  trivial,  like
  1878. "sum"  or  "addone".   For  on-the-spot use in cases like this, we can use an
  1879. anonymous function called a lambda-expression.   Here's  a  lambda-expression
  1880. corresponding to "sum":
  1881.  
  1882.  
  1883.      lambda ( x, y ) => x + y
  1884.  
  1885.  
  1886. The symbol "lambda" introduces the function and  "x" and "y" are  its  formal
  1887. parameters.   The  expression  "x  + y" is the function definition.  The part
  1888. after "lambda" is just a recursion equation without the "---" and  with  "=>"
  1889. instead  of  "<=".   Here's  another  lambda-expression  used  as  the actual
  1890. parameter of "reduce":
  1891.  
  1892.  
  1893.      reduce ( [ "toe","tac","tic" ], lambda ( a, b ) => b <> a, nil ) ;
  1894.      "tictactoe" : list ( char )
  1895.  
  1896.  
  1897. There can be more than one recursion  equation  in  the  lambda-  expression.
  1898. They're  separated  from each other by the symbol "|" and pattern-matching is
  1899. used  to  select  the  appropriate  one.   Here's  an   example   that   uses
  1900. pattern-matching  in  a  lambda-expression to avoid division by zero when the
  1901. function it defines is executed:
  1902.  
  1903.  
  1904.      map ( [ 1,0,2,0,3 ], lambda ( 0 )          => 0
  1905.                           | ( succ ( n ) ) => 100 div succ ( n ) ) ;
  1906.      [ 100,0,50,0,33 ] : list ( num )
  1907.  
  1908.  
  1909.  
  1910.  
  1911. Hope Tutorial                                                         Page 29
  1912.  
  1913.  
  1914.  
  1915.  
  1916.  
  1917.  
  1918.  
  1919.  
  1920.  
  1921.  
  1922.                      Functions that create new functions
  1923.  
  1924. As we've seen, Hope functions possess 'full rights'  and  can  be  passed  as
  1925. actual  parameters  like any data object.  It'll be no surprise to learn that
  1926. we can return a function as the result of another function.  The  result  can
  1927. be  a named function or an anonymous function defined by a lambda-expression.
  1928. Here's a simple example:
  1929.  
  1930.  
  1931.      dec makestep : num -> ( num -> num ) ;
  1932.      --- makestep ( i ) <= lambda ( x ) => i + x ;
  1933.  
  1934.  
  1935.      makestep ( 3 ) ;
  1936.      lambda ( x ) => 3 + x : num -> num
  1937.  
  1938.  
  1939. As we can see from trying "makestep", its result  is  an  anonymous  function
  1940. that adds a fixed quantity to its single argument.  The size of the increment
  1941. was specified as an actual parameter to "makestep" when the new function  was
  1942. created,  and  has  become  'bound  in' to its definition.  If we try the new
  1943. function, we'll see that it really does add "3" to its actual parameter:
  1944.  
  1945.  
  1946.      makestep ( 3 ) ( 10 ) ;
  1947.      13 : num
  1948.  
  1949.  
  1950. There are two applications here.  First we apply "makestep" to "3", then  the
  1951. resulting  anonymous function is applied to "10".  Finally, here's a function
  1952. that has functions as both actual parameter and result:
  1953.  
  1954.  
  1955.      dec twice : ( alpha -> alpha ) -> ( alpha -> alpha ) ;
  1956.      --- twice ( f ) <= lambda ( x ) => f ( f ( x ) ) ;
  1957.  
  1958.  
  1959. "twice" defines a new function that has a  single  argument  and  some  other
  1960. function  "f"  bound into its definition.  The new function has the same type
  1961. as "f".  We can see its effect using a simple function like "square":
  1962.  
  1963.  
  1964.      twice ( square ) ;
  1965.      lambda ( x ) => square( square ( x ) ) : num -> num
  1966.  
  1967.      twice ( square ) ( 3 ) ;
  1968.      81 : num
  1969.  
  1970.  
  1971.  
  1972.  
  1973.  
  1974.  
  1975.  
  1976.  
  1977. Hope Tutorial                                                         Page 30
  1978.  
  1979.  
  1980.  
  1981.  
  1982.  
  1983.  
  1984.  
  1985.  
  1986.  
  1987.  
  1988. The new function applies the bound-in function to its argument twice.  We can
  1989. even  bind  in  "twice"  itself,  generating a new function that behaves like
  1990. "twice" except that the function eventually bound in  will  be  applied  four
  1991. times:
  1992.  
  1993.  
  1994.      twice ( twice ) ;
  1995.      lambda ( x ) => twice ( twice ( x ) )
  1996.      : ( alpha -> alpha ) -> ( alpha -> alpha )
  1997.  
  1998.      twice ( twice ) ( square ) ( 3 ) ;
  1999.      43046721 : num
  2000.  
  2001.  
  2002.  
  2003.  
  2004.  
  2005.  
  2006.  
  2007.  
  2008.  
  2009.  
  2010.  
  2011.  
  2012.  
  2013.  
  2014.  
  2015.  
  2016.  
  2017.  
  2018.  
  2019.  
  2020.  
  2021.  
  2022.  
  2023.  
  2024.  
  2025.  
  2026.  
  2027.  
  2028.  
  2029.  
  2030.  
  2031.  
  2032.  
  2033.  
  2034.  
  2035.  
  2036.  
  2037.  
  2038.  
  2039.  
  2040.  
  2041.  
  2042.  
  2043. Hope Tutorial                                                         Page 31
  2044.  
  2045.  
  2046.  
  2047.  
  2048.  
  2049.  
  2050.  
  2051.  
  2052.  
  2053.  
  2054.                                 In conclusion
  2055.  
  2056. In this article you've been introduced to the ideas of functional programming
  2057. through  one  of  the  new generation of functional languages.  You saw how a
  2058. Hope program is just a series of functions that are regarded  as  definitions
  2059. of  parts  of  a  data structure - the 'results' of the program - and how the
  2060. powerful idea of higher-order functions allows  us  to  capture  many  common
  2061. program patterns in a single function.
  2062.  
  2063.  
  2064. Some of these ideas will already be familiar  to  users  of  LISP,  but  they
  2065. appear  in a purer form in Hope, because there are no mechanisms for updating
  2066. data structures like LISP's SETQ and RPLACA or for specifying  the  order  of
  2067. evaluation  like  GO  and PROG.  Unlike LISP programs, Hope programs are free
  2068. from side-effects  and  possess  the  mathematical  property  of  referential
  2069. transparency.
  2070.  
  2071.  
  2072. You also saw features that are primitive or  lacking  in  LISP  and  in  most
  2073. imperative  languages.   The  data  declaration  lets you define complex data
  2074. types without worrying about how they're represented  and  pattern-  matching
  2075. lets  you decompose them, so you can use abstract data types directly without
  2076. writing access procedures and without the need to invent lots of  new  names.
  2077. The  typing  mechanism lets the compiler check that you're using data objects
  2078. in a correct and consistent way, while the idea of  polymorphic  types  stops
  2079. the  checking  from  being  too  restrictive  and lets you define common data
  2080. 'shapes' with a single function.
  2081.  
  2082.  
  2083. Higher-order functions and polymorphic types allow us to write programs  that
  2084. are  very  concise.   Programmers  are more productive and their programs are
  2085. easier to understand and  to  reason  about.   The  property  of  referential
  2086. transparency  improves our ability to reason about programs still further and
  2087. makes it possible to transform them mechanically into programs that are still
  2088. provably correct, but more efficient in their use of space or time.  Finally,
  2089. referential transparency keeps the meaning of Hope  programs  independent  of
  2090. the  order they're evaluated in, making them ideal for parallel evaluation on
  2091. suitable machines.  You'll be seeing a lot more of Hope and languages like it
  2092. in the future.
  2093.  
  2094.  
  2095.  
  2096.  
  2097.  
  2098.  
  2099.  
  2100.  
  2101.  
  2102.  
  2103.  
  2104.  
  2105.  
  2106.  
  2107.  
  2108.  
  2109. Hope Tutorial                                                         Page 32
  2110.  
  2111.  
  2112.  
  2113.